/*
 * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
 * Copyright (C) 2006 - Javolution (http://javolution.org/)
 * All rights reserved.
 * 
 * Permission to use, copy, modify, and distribute this software is
 * freely granted, provided that this notice is preserved.
 */
package javolution.xml.stream;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;

import javolution.context.ObjectFactory;
import javolution.io.UTF8StreamWriter;
import javolution.lang.Reusable;
import javolution.text.CharArray;
import javolution.text.TextBuilder;
import j2me.lang.CharSequence;
import j2me.lang.IllegalStateException;
import j2mex.realtime.MemoryArea;

/**
 * <p> This class represents a  {@link javolution.lang.Reusable reusable}
 *     implementation of {@link XMLStreamWriter}.</p>
 *
 * @author  <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
 * @version 4.0, September 4, 2006
 */
public final class XMLStreamWriterImpl implements XMLStreamWriter, Reusable {

	/** 
	 * Holds the length of intermediate buffer.
	 */
	private static final int BUFFER_LENGTH = 2048;

	/** 
	 * Holds the current nesting level.
	 */
	private int _nesting = 0;

	/** 
	 * Holds the element qualified name (indexed per nesting level)
	 */
	private TextBuilder[] _qNames = new TextBuilder[16];

	/** 
	 * Indicates if the current element is open.
	 */
	private boolean _isElementOpen;

	/** 
	 * Indicates if the current element is an empty element.
	 */
	private boolean _isEmptyElement;

	/** 
	 * Holds intermediate buffer.
	 */
	private final char[] _buffer = new char[BUFFER_LENGTH];

	/** 
	 * Holds the namespace stack.
	 */
	private final NamespacesImpl _namespaces = new NamespacesImpl();

	/** 
	 * Holds the buffer current index.
	 */
	private int _index;

	/** 
	 * Holds repairing namespace property.
	 */
	private boolean _isRepairingNamespaces;

	/** 
	 * Holds repairing prefix property.
	 */
	private String _repairingPrefix = "ns";

	/** 
	 * Holds indentation property.
	 */
	private String _indentation;

	/** 
	 * Holds current indentation level.
	 */
	private int _indentationLevel;

	/** 
	 * Holds automatic empty elements  property.
	 */
	private boolean _automaticEmptyElements;

	/** 
	 * Holds counter for automatic namespace generation.
	 */
	private int _autoNSCount;

	/**
	 * Holds recycling factory if any.
	 */
	ObjectFactory _objectFactory;

	////////////////////////
	// Temporary Settings //
	////////////////////////

	/** 
	 * Holds the writer destination (<code>null</code> when unused).
	 */
	private Writer _writer;

	/** 
	 * Holds the encoding  (<code>null</code> if N/A).
	 */
	private String _encoding;

	/**
	 * Holds the defautl writer for output streams.
	 */
	private final UTF8StreamWriter _utf8StreamWriter = new UTF8StreamWriter();

	/** 
	 * Default constructor.
	 */
	public XMLStreamWriterImpl() {
		for (int i = 0; i < _qNames.length;) {
			_qNames[i++] = new TextBuilder();
		}
	}

	/**
	 * Sets the output stream destination for this XML stream writer 
	 * (UTF-8 encoding).
	 *
	 * @param out the output source with utf-8 encoding.
	 */
	public void setOutput(OutputStream out) throws XMLStreamException {
		_utf8StreamWriter.setOutput(out);
		_encoding = "UTF-8";
		setOutput(_utf8StreamWriter);
	}

	/**
	 * Sets the output stream destination and encoding for this XML stream 
	 * writer.
	 *
	 * @param out the output source.
	 * @param encoding the associated encoding.
	 * @throws XMLStreamException if the specified encoding is not supported.
	 */
	public void setOutput(OutputStream out, String encoding)
			throws XMLStreamException {
		if (encoding.equals("UTF-8") || encoding.equals("utf-8")
				|| encoding.equals("ASCII")) {
			setOutput(out); // Default encoding.
		} else {
			try {
				_encoding = encoding;
				setOutput(new OutputStreamWriter(out, encoding));
			} catch (UnsupportedEncodingException e) {
				throw new XMLStreamException(e);
			}
		}
	}

	/**
	 * Sets the writer output destination for this XML stream writer. 
	 *
	 * @param  writer the output destination writer.
	 * @see    javolution.io.UTF8StreamWriter
	 * @see    javolution.io.UTF8ByteBufferWriter
	 * @see    javolution.io.AppendableWriter
	 */
	public void setOutput(Writer writer) throws XMLStreamException {
		if (_writer != null)
			throw new IllegalStateException("Writer not closed or reset");
		_writer = writer;
	}

	/** 
	 * Requires this writer to create a new prefix when a namespace has none
	 * (default <code>false</code>).
	 * 
	 * @param isRepairingNamespaces <code>true</code> if namespaces 
	 *        are repaired; <code>false</code> otherwise.
	 */
	public void setRepairingNamespaces(boolean isRepairingNamespaces) {
		_isRepairingNamespaces = isRepairingNamespaces;
	}

	/** 
	 * Specifies the prefix to be append by a trailing part 
	 * (a sequence number) in order to make it unique to be usable as
	 * a temporary non-colliding prefix when repairing namespaces
	 * (default <code>"ns"</code>).
	 * 
	 * @param repairingPrefix the prefix root.
	 */
	public void setRepairingPrefix(String repairingPrefix) {
		_repairingPrefix = repairingPrefix;
	}

	/** 
	 * Specifies the indentation string; non-null indentation 
	 * forces the writer to write elements into separate lines
	 * (default <code>null</code>).
	 * 
	 * @param indentation the indentation string.
	 */
	public void setIndentation(String indentation) {
		_indentation = indentation;
	}

	/** 
	 * Requires this writer to automatically output empty elements when a 
	 * start element is immediately followed by matching end element
	 * (default <code>false</code>).
	 * 
	 * @param automaticEmptyElements <code>true</code> if start element 
	 *        immediately followed by end element results in an empty element
	 *        beign written; <code>false</code> otherwise.
	 */
	public void setAutomaticEmptyElements(boolean automaticEmptyElements) {
		_automaticEmptyElements = automaticEmptyElements;
	}

	// Implements reusable.
	public void reset() {
		_automaticEmptyElements = false;
		_autoNSCount = 0;
		_encoding = null;
		_indentation = null;
		_indentationLevel = 0;
		_index = 0;
		_isElementOpen = false;
		_isEmptyElement = false;
		_isRepairingNamespaces = false;
		_namespaces.reset();
		_nesting = 0;
		_objectFactory = null;
		_repairingPrefix = "ns";
		_utf8StreamWriter.reset();
		_writer = null;
	}

	// Implements XMLStreamWriter interface.
	public void writeStartElement(CharSequence localName)
			throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		writeNewElement(null, localName, null);
	}

	// Implements XMLStreamWriter interface.
	public void writeStartElement(CharSequence namespaceURI,
			CharSequence localName) throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		if (namespaceURI == null)
			throw new XMLStreamException("Namespace URI cannot be null");
		writeNewElement(null, localName, namespaceURI);
	}

	// Implements XMLStreamWriter interface.
	public void writeStartElement(CharSequence prefix, CharSequence localName,
			CharSequence namespaceURI) throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		if (namespaceURI == null)
			throw new XMLStreamException("Namespace URI cannot be null");
		if (prefix == null)
			throw new XMLStreamException("Prefix cannot be null");
		writeNewElement(prefix, localName, namespaceURI);
	}

	// Implements XMLStreamWriter interface.
	public void writeEmptyElement(CharSequence localName)
			throws XMLStreamException {
		writeStartElement(localName);
		_isEmptyElement = true;
	}

	// Implements XMLStreamWriter interface.
	public void writeEmptyElement(CharSequence namespaceURI,
			CharSequence localName) throws XMLStreamException {
		writeStartElement(namespaceURI, localName);
		_isEmptyElement = true;
	}

	// Implements XMLStreamWriter interface.
	public void writeEmptyElement(CharSequence prefix, CharSequence localName,
			CharSequence namespaceURI) throws XMLStreamException {
		writeStartElement(prefix, localName, namespaceURI);
		_isEmptyElement = true;
	}

	// Implements XMLStreamWriter interface.
	public void writeEndElement() throws XMLStreamException {
		if (_isElementOpen) { // Empty element.
		    if (_isEmptyElement) { // Closes the empty element tag.
                        closeOpenTag();
		    } else { // Start element open.
		        if (_automaticEmptyElements) { // Do as if empty element written. 		    
				_isEmptyElement = true; 
				closeOpenTag();
				return;
			} else { // Closes the start element tag.
			   closeOpenTag();
			}
		    }
		}
		if ((_indentation != null) && (_indentationLevel != _nesting - 1)) {
			// Do not indent if no change in indentation level
			// to avoid interfering with text only elements.
			write('\n');
			for (int i = 1; i < _nesting; i++) {
				writeStr(_indentation);
			}
		}

		write('<');
		write('/');
		writeTB(_qNames[_nesting--]);
		write('>');
		_namespaces.pop();
	}

	// Implements XMLStreamWriter interface.
	public void writeEndDocument() throws XMLStreamException {
		if (_isElementOpen)
			closeOpenTag();
		while (_nesting > 0) { // Implicits closing of all elements.
			writeEndElement();
		}
	}

	// Implements XMLStreamWriter interface.
	public void close() throws XMLStreamException {
		if (_writer != null) {
			if (_nesting != 0) { // Closes all elements.
				writeEndDocument();
			}
			flush();
		}
		if (_objectFactory != null) {
			_objectFactory.recycle(this); // Implicit reset.
		} else {
			reset(); // Explicit reset.
		}
	}

	// Implements XMLStreamWriter interface.
	public void flush() throws XMLStreamException {
		flushBuffer();
		try {
			_writer.flush();
		} catch (IOException e) {
			throw new XMLStreamException(e);
		}
	}

	// Implements XMLStreamWriter interface.
	public void writeAttribute(CharSequence localName, CharSequence value)
			throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		if (value == null)
			throw new XMLStreamException("Value cannot be null");
		writeAttributeOrNamespace(null, null, localName, value);
	}

	// Implements XMLStreamWriter interface.
	public void writeAttribute(CharSequence namespaceURI,
			CharSequence localName, CharSequence value)
			throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		if (value == null)
			throw new XMLStreamException("Value cannot be null");
		if (namespaceURI == null)
			throw new XMLStreamException("Namespace URI cannot be null");
		writeAttributeOrNamespace(null, namespaceURI, localName, value);
	}

	// Implements XMLStreamWriter interface.
	public void writeAttribute(CharSequence prefix, CharSequence namespaceURI,
			CharSequence localName, CharSequence value)
			throws XMLStreamException {
		if (localName == null)
			throw new XMLStreamException("Local name cannot be null");
		if (value == null)
			throw new XMLStreamException("Value cannot be null");
		if (namespaceURI == null)
			throw new XMLStreamException("Namespace URI cannot be null");
		if (prefix == null)
			throw new XMLStreamException("Prefix cannot be null");
		writeAttributeOrNamespace(prefix, namespaceURI, localName, value);
	}

	// Implements XMLStreamWriter interface.
	public void writeNamespace(CharSequence prefix, CharSequence namespaceURI)
			throws XMLStreamException {
		if ((prefix == null) || (prefix.length() == 0)
				|| _namespaces._xmlns.equals(prefix)) {
			prefix = _namespaces._defaultNsPrefix;
		}
		if (!_isElementOpen) // Check now as the actual writting is queued.
			throw new IllegalStateException("No open start element");
		_namespaces.setPrefix(prefix,
				(namespaceURI == null) ? _namespaces._nullNsURI : namespaceURI,
				true);
	}

	// Implements XMLStreamWriter interface.
	public void writeDefaultNamespace(CharSequence namespaceURI)
			throws XMLStreamException {
		writeNamespace(_namespaces._defaultNsPrefix, namespaceURI);
	}

	// Implements XMLStreamWriter interface.
	public void writeComment(CharSequence data) throws XMLStreamException {
		if (_isElementOpen)
			closeOpenTag();
		writeStr("<!--");
		if (data != null) { // null values allowed.
			write(data);
		}
		writeStr("-->");
	}

	// Implements XMLStreamWriter interface.
	public void writeProcessingInstruction(CharSequence target)
			throws XMLStreamException {
		writeProcessingInstruction(target, _noChar);
	}

	private final CharArray _noChar = new CharArray("");

	// Implements XMLStreamWriter interface.
	public void writeProcessingInstruction(CharSequence target,
			CharSequence data) throws XMLStreamException {
		if (target == null)
			throw new XMLStreamException("Target cannot be null");
		if (data == null)
			throw new XMLStreamException("Data cannot be null");
		if (_isElementOpen)
			closeOpenTag();
		writeStr("<?");
		write(target);
		write(' ');
		write(data);
		write(" ?>");
	}

	// Implements XMLStreamWriter interface.
	public void writeCData(CharSequence data) throws XMLStreamException {
		if (data == null)
			throw new XMLStreamException("Data cannot be null");
		if (_isElementOpen)
			closeOpenTag();
		writeStr("<![CDATA[");
		write(data);
		writeStr("]]>");
	}

	// Implements XMLStreamWriter interface.
	public void writeDTD(CharSequence dtd) throws XMLStreamException {
		if (dtd == null)
			throw new XMLStreamException("DTD cannot be null");
		if (_nesting > 0)
			throw new XMLStreamException(
					"DOCTYPE declaration (DTD) when not in document root (prolog)");
		write(dtd);
	}

	// Implements XMLStreamWriter interface.
	public void writeEntityRef(CharSequence name) throws XMLStreamException {
		write('&');
		write(name);
		write(';');
	}

	// Implements XMLStreamWriter interface.
	public void writeStartDocument() throws XMLStreamException {
		writeStartDocument(null, null);
	}

	// Implements XMLStreamWriter interface.
	public void writeStartDocument(CharSequence version)
			throws XMLStreamException {
		writeStartDocument(null, version);
	}

	// Implements XMLStreamWriter interface.
	public void writeStartDocument(CharSequence encoding, CharSequence version)
			throws XMLStreamException {
		if (_nesting > 0)
			throw new XMLStreamException("Not in document root");
		writeStr("<?xml version=\"");
		if (version != null) {
			write(version);
			write('"');
		} else { // Default to 1.0
			writeStr("1.0\"");
		}
		if (encoding != null) {
			writeStr(" encoding=\"");
			write(encoding);
			write('"');
		} else if (_encoding != null) { // Use init encoding (if any).
			writeStr(" encoding=\"");
			writeStr(_encoding);
			write('"');
		}
		writeStr(" ?>");
	}

	// Implements XMLStreamWriter interface.
	public void writeCharacters(CharSequence text) throws XMLStreamException {
		if (_isElementOpen)
			closeOpenTag();
		if (text == null)
			return;
		writeEsc(text);
	}

	// Implements XMLStreamWriter interface.
	public void writeCharacters(char[] text, int start, int length)
			throws XMLStreamException {
        if (_isElementOpen) 
            closeOpenTag();
		for (int i = start, end = start + length; i < end;) {
			writeEsc(text[i++]);
		}
	}

	// Implements XMLStreamWriter interface.
	public CharSequence getPrefix(CharSequence uri) throws XMLStreamException {
		return _namespaces.getPrefix(uri);
	}

	// Implements XMLStreamWriter interface.
	public void setPrefix(CharSequence prefix, CharSequence uri)
			throws XMLStreamException {
		_namespaces.setPrefix(prefix, (uri == null) ? _namespaces._nullNsURI
				: uri, false);
	}

	// Implements XMLStreamWriter interface.
	public void setDefaultNamespace(CharSequence uri) throws XMLStreamException {
		setPrefix(_namespaces._defaultNsPrefix, uri);
	}

	// Implements XMLStreamWriter interface.
	public Object getProperty(String name) throws IllegalArgumentException {
		if (name.equals(XMLOutputFactory.IS_REPAIRING_NAMESPACES)) {
			return new Boolean(_isRepairingNamespaces);
		} else if (name.equals(XMLOutputFactory.REPAIRING_PREFIX)) {
			return _repairingPrefix;
		} else if (name.equals(XMLOutputFactory.AUTOMATIC_EMPTY_ELEMENTS)) {
			return new Boolean(_automaticEmptyElements);
		} else if (name.equals(XMLOutputFactory.INDENTATION)) {
			return _indentation;
		} else {
			throw new IllegalArgumentException("Property: " + name
					+ " not supported");
		}
	}

	// Writes a new start or empty element.
	private void writeNewElement(CharSequence prefix, CharSequence localName,
			CharSequence namespaceURI) throws XMLStreamException {

		// Close any open element and gets ready to write a new one.
		if (_isElementOpen)
			closeOpenTag();
		if (_indentation != null) {
			write('\n');
			_indentationLevel = _nesting;
			for (int i = 0; i < _indentationLevel; i++) {
				writeStr(_indentation);
			}
		}
		write('<');
		_isElementOpen = true;

		// Enters a new local scope.
		if (++_nesting >= _qNames.length)
			resizeElemStack();
		_namespaces.push();

		// Constructs qName.
		TextBuilder qName = _qNames[_nesting].clear();

		// Writes prefix if any.
		if ((namespaceURI != null)
				&& (!_namespaces._defaultNamespace.equals(namespaceURI))) {
			if (_isRepairingNamespaces) { // Repairs prefix.
				prefix = getRepairedPrefix(prefix, namespaceURI);
			} else if (prefix == null) { // Retrieves prefix.
				prefix = getPrefix(namespaceURI);
				if (prefix == null)
					throw new XMLStreamException("URI: " + namespaceURI
							+ " not bound and repairing namespaces disabled");
			}
			if (prefix.length() > 0) {
				qName.append(prefix);
				qName.append(':');
			}
		}
		qName.append(localName);
		writeTB(qName);
	}

	// Writes a new attribute.
	private void writeAttributeOrNamespace(CharSequence prefix,
			CharSequence namespaceURI, CharSequence localName,
			CharSequence value) throws XMLStreamException {
		if (!_isElementOpen)
			throw new IllegalStateException("No open start element");
		write(' ');

		// Writes prefix if any.
		if ((namespaceURI != null)
				&& (!_namespaces._defaultNamespace.equals(namespaceURI))) {
			if (_isRepairingNamespaces) { // Repairs prefix if current prefix is not correct.
				prefix = getRepairedPrefix(prefix, namespaceURI);
			} else if (prefix == null) {
				prefix = getPrefix(namespaceURI);
				if (prefix == null)
					throw new XMLStreamException("URI: " + namespaceURI
							+ " not bound and repairing namespaces disabled");
			}
			if (prefix.length() > 0) {
				write(prefix);
				write(':');
			}
		}

		write(localName);
		write('=');
		write('"');
		writeEsc(value);
		write('"');
	}

	// Closes the current element (scope if empty element).
	private void closeOpenTag() throws XMLStreamException {

		// Writes namespaces now.
		writeNamespaces();

		// Closes the tag.
		_isElementOpen = false;
		if (_isEmptyElement) {
			write('/');
			write('>');
			_nesting--;
			_namespaces.pop();
			_isEmptyElement = false;
		} else {
			write('>');
		}
	}

	// Writes all namespaces, these include namespaces set but 
	// not written in outer scope.
	private void writeNamespaces() throws XMLStreamException {
		int i0 = (_nesting > 1) ? _namespaces._namespacesCount[_nesting - 2]
				: NamespacesImpl.NBR_PREDEFINED_NAMESPACES;
		int i1 = _namespaces._namespacesCount[_nesting - 1];
		int i2 = _namespaces._namespacesCount[_nesting];
		for (int i = i0; i < i2; i++) {
			if (((_isRepairingNamespaces && (i < i1) && !_namespaces._prefixesWritten[i]))
					|| ((i >= i1) && _namespaces._prefixesWritten[i])) { // Write namespace.

				// In repairing mode, removes redondancy.
				if (_isRepairingNamespaces) {
					CharArray prefix = _namespaces.getPrefix(
							_namespaces._namespaces[i], i);
					if (_namespaces._prefixes[i].equals(prefix))
						continue; // Not necessary.
				} // Direct mode, just write them as requested (no check).

				// Writes namespace.
				if (_namespaces._prefixes[i].length() == 0) { // Default namespace.
					writeAttributeOrNamespace(null, null, _namespaces._xmlns,
							_namespaces._namespaces[i]);
				} else {
					writeAttributeOrNamespace(_namespaces._xmlns,
							_namespaces._xmlnsURI, _namespaces._prefixes[i],
							_namespaces._namespaces[i]);
				}
			}
		}
	}

	// Returns the prefix for the specified namespace.
	private CharSequence getRepairedPrefix(CharSequence prefix,
			CharSequence namespaceURI) throws XMLStreamException {
		CharArray prefixForURI = _namespaces.getPrefix(namespaceURI);
		if ((prefixForURI != null)
				&& ((prefix == null) || prefixForURI.equals(prefix)))
			return prefixForURI; // No repair needed.
		if ((prefix == null) || (prefix.length() == 0)) { // Creates new prefix.
			prefix = _autoPrefix.clear().append(_repairingPrefix).append(
					_autoNSCount++);
		}
		_namespaces.setPrefix(prefix, namespaceURI, true); // Map to namespace URI.
		return prefix;
	}

	private final TextBuilder _autoPrefix = new TextBuilder();

	// Resizes element stack  (same memory area as the writer).
	private void resizeElemStack() {
		MemoryArea.getMemoryArea(this).executeInArea(new Runnable() {
			public void run() {
				final int oldLength = _qNames.length;
				final int newLength = oldLength * 2;

				// Resizes elements qNames stack.
				TextBuilder[] tmp = new TextBuilder[newLength];
				System.arraycopy(_qNames, 0, tmp, 0, oldLength);
				_qNames = tmp;
				for (int i = oldLength; i < newLength; i++) {
					_qNames[i] = new TextBuilder();
				}
			}
		});
	}

	// Writes methods.
	//

	private final void write(char c) throws XMLStreamException {
		_buffer[_index++] = c;
		if (_index == BUFFER_LENGTH) {
			flushBuffer();
		}
	}

	private final void writeEsc(char c) throws XMLStreamException {
		if ((c >= '?')
				|| ((c >= ' ') && (c != '<') && (c != '>') && (c != '"') && (c != '&'))) { // Most common case.
			write(c);
		} else {
			writeEsc2(c);
		}
	}

	private final void writeEsc2(char c) throws XMLStreamException {
		switch (c) {
		case '<':
			writeStr("&lt;");
			break;
		case '>':
			writeStr("&gt;");
			break;
		// case '\'': // Unnecessary due to exclusive use of "
		// writeStr("&apos;");
		// break;
		case '"':
			writeStr("&quot;");
			break;
		case '&':
			writeStr("&amp;");
			break;
		default:
			if (c >= ' ') {
				write(c);
			} else {
				writeStr("&#");
				write((char) ('0' + c / 10));
				write((char) ('0' + c % 10));
				write(';');
			}
		}
	}

	private void write(Object obj) throws XMLStreamException {
		if (obj instanceof String) {
			writeStr((String) obj);
		} else {
			writeCsq((CharSequence) obj);
		}
	}

	private void writeStr(String str) throws XMLStreamException {
		int n = str.length();
		if (_index + n < BUFFER_LENGTH) {
			str.getChars(0, n, _buffer, _index);
			_index += n;
		} else {
			writeStrImmediate(str);
		}
	}

	private void writeStrImmediate(String str) throws XMLStreamException {
		flushBuffer();
		try {
			_writer.write(str);
		} catch (IOException e) {
			throw new XMLStreamException(e);
		}
	}

	private void writeCsq(CharSequence csq) throws XMLStreamException {
		for (int i = 0, n = csq.length(); i < n;) {
			// Inline write(char)
			_buffer[_index++] = csq.charAt(i++);
			if (_index == BUFFER_LENGTH) {
				flushBuffer();
			}
		}
	}

	private void writeTB(TextBuilder tb) throws XMLStreamException {
		for (int i = 0, n = tb.length(); i < n;) {
			// Inline write(char)
			_buffer[_index++] = tb.charAt(i++);
			if (_index == BUFFER_LENGTH) {
				flushBuffer();
			}
		}
	}

	private void writeEsc(Object obj) throws XMLStreamException {
		if (obj instanceof String) {
			writeEscStr((String) obj);
		} else {
			writeEscCsq((CharSequence) obj);
		}
	}

	private void writeEscStr(String str) throws XMLStreamException {
		for (int i = 0, n = str.length(); i < n;) {
			// Inline writeEsc(char)
			char c = str.charAt(i++);
			if ((c >= '?')
					|| ((c >= ' ') && (c != '<') && (c != '>') && (c != '"') && (c != '&'))) { // Most common case.
				// Inline write(char)
				_buffer[_index++] = c;
				if (_index == BUFFER_LENGTH) {
					flushBuffer();
				}
			} else {
				writeEsc2(c);
			}
		}
	}

	private void writeEscCsq(CharSequence csq) throws XMLStreamException {
		for (int i = 0, n = csq.length(); i < n;) {
			// Inline writeEsc(char)
			char c = csq.charAt(i++);
			if ((c >= '?')
					|| ((c >= ' ') && (c != '<') && (c != '>') && (c != '"') && (c != '&'))) { // Most common case.
				// Inline write(char)
				_buffer[_index++] = c;
				if (_index == BUFFER_LENGTH) {
					flushBuffer();
				}
			} else {
				writeEsc2(c);
			}
		}
	}

	private void flushBuffer() throws XMLStreamException {
		try {
			_writer.write(_buffer, 0, _index);
		} catch (IOException e) {
			throw new XMLStreamException(e);
		} finally {
			_index = 0;
		}
	}

}